// Generate boilerplate code for setting similar structs from each other

//go:build ignore

package main

import (
	"flag"
	"fmt"
	"io"
	"log"
	"os"
	"reflect"
	"strings"

	"github.com/aws/aws-sdk-go/service/s3"
)

// flags
var (
	outputFile = flag.String("o", "", "Output file name, stdout if unset")
)

// globals
var (
	out io.Writer = os.Stdout
)

// genSetFrom generates code to set the public members of a from b
//
// a and b should be pointers to structs
//
// a can be a different type from b
//
// Only the Fields which have the same name and assignable type on a
// and b will be set.
//
// This is useful for copying between almost identical structures that
// are frequently present in auto-generated code for cloud storage
// interfaces.
func genSetFrom(a, b interface{}) {
	name := fmt.Sprintf("setFrom_%T_%T", a, b)
	name = strings.Replace(name, ".", "", -1)
	name = strings.Replace(name, "*", "", -1)
	fmt.Fprintf(out, "\n// %s copies matching elements from a to b\n", name)
	fmt.Fprintf(out, "func %s(a %T, b %T) {\n", name, a, b)
	ta := reflect.TypeOf(a).Elem()
	tb := reflect.TypeOf(b).Elem()
	va := reflect.ValueOf(a).Elem()
	vb := reflect.ValueOf(b).Elem()
	for i := 0; i < tb.NumField(); i++ {
		bField := vb.Field(i)
		tbField := tb.Field(i)
		name := tbField.Name
		aField := va.FieldByName(name)
		taField, found := ta.FieldByName(name)
		if found && aField.IsValid() && bField.IsValid() && aField.CanSet() && tbField.Type.AssignableTo(taField.Type) {
			fmt.Fprintf(out, "\ta.%s = b.%s\n", name, name)
		}
	}
	fmt.Fprintf(out, "}\n")
}

func main() {
	flag.Parse()

	if *outputFile != "" {
		fd, err := os.Create(*outputFile)
		if err != nil {
			log.Fatal(err)
		}
		defer func() {
			err := fd.Close()
			if err != nil {
				log.Fatal(err)
			}
		}()
		out = fd
	}

	fmt.Fprintf(out, `// Code generated by "go run gen_setfrom.go"; DO NOT EDIT.

package s3

import "github.com/aws/aws-sdk-go/service/s3"
`)

	genSetFrom(new(s3.ListObjectsInput), new(s3.ListObjectsV2Input))
	genSetFrom(new(s3.ListObjectsV2Output), new(s3.ListObjectsOutput))
	genSetFrom(new(s3.ListObjectVersionsInput), new(s3.ListObjectsV2Input))
	genSetFrom(new(s3.ObjectVersion), new(s3.DeleteMarkerEntry))
	genSetFrom(new(s3.ListObjectsV2Output), new(s3.ListObjectVersionsOutput))
	genSetFrom(new(s3.Object), new(s3.ObjectVersion))
	genSetFrom(new(s3.CreateMultipartUploadInput), new(s3.HeadObjectOutput))
	genSetFrom(new(s3.CreateMultipartUploadInput), new(s3.CopyObjectInput))
	genSetFrom(new(s3.UploadPartCopyInput), new(s3.CopyObjectInput))
	genSetFrom(new(s3.HeadObjectOutput), new(s3.GetObjectOutput))
	genSetFrom(new(s3.CreateMultipartUploadInput), new(s3.PutObjectInput))
	genSetFrom(new(s3.HeadObjectOutput), new(s3.PutObjectInput))
	genSetFrom(new(s3.CopyObjectInput), new(s3.PutObjectInput))
}
